package com.sm2cppa.controller;

import com.sm2cppa.contract.MapPkToTx;
import com.sm2cppa.entity.BatchRequest;
import com.sm2cppa.entity.ResponseBody;
import com.sm2cppa.entity.UserBody;
import com.sm2cppa.entity.UserMap;
import com.sm2cppa.utils.KeyDerive;
import com.sm2cppa.utils.SM2Utils.Util;
import com.sm2cppa.utils.Transaction;
import org.fisco.bcos.sdk.BcosSDK;
import org.fisco.bcos.sdk.client.Client;
import org.fisco.bcos.sdk.client.protocol.response.BcosTransaction;
import org.fisco.bcos.sdk.crypto.keypair.CryptoKeyPair;
import org.fisco.bcos.sdk.utils.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;


@RestController
public class CAController {
    public  String map_addr;
    
    @Value("${caaddr}")
    private String ca_addr;
    @Value("${user1addr}")
    private String user1_addr;
    @Value("${user2addr}")
    private String user2_addr;

    private Client client;

    @Autowired
    private ClientController clientController;

    @Autowired
    private Transaction transaction;

    @Autowired
    public UserMap usermap;


    @PostConstruct
    public void init(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:ca-config.xml");
        // 初始化BcosSDK
        BcosSDK sdk =  context.getBean(BcosSDK.class);
        // 为群组1初始化client
        client = sdk.getClient(Integer.valueOf(1));
        context.close();

        System.out.println("-------------------------Build Success-------------------------------");
        System.out.println("------------------ Please  Deploy MapPkToTx -------------------------");
    }

    @PostMapping("/deployContract")
    public ResponseBody mapDeploy(@RequestBody UserBody userBody) throws Exception {
        ResponseBody response = new ResponseBody();
        String CAtoken=userBody.getToken();
        if(!Objects.equals(CAtoken,ca_addr)){
            response.setMessage("token error");
            return response;
        }

        CryptoKeyPair cryptoKeyPair = client.getCryptoSuite().getCryptoKeyPair();
        long startTime = System.nanoTime();
        MapPkToTx mapPkToTx=MapPkToTx.deploy(client,cryptoKeyPair);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;

        map_addr=mapPkToTx.getContractAddress();
        clientController.map_addr=map_addr;
        response.setMessage("MapPkToTx address："+map_addr);
        System.out.println("MapPkToTx address："+map_addr);
        System.out.println("MapPkToTx.deploy: "+duration+"ms");
        response.setMessage(map_addr);
        response.setRuntime(duration+"ms");
        response.setStatus(1);
        return response;
    }

    @PostMapping("/InitialUser")
    public String InitialUser(@RequestBody UserBody userBody) throws Exception {
        ResponseBody response = new ResponseBody();
        String useraddr= userBody.getAddress();
        String key= userBody.getKey();
        String chain= userBody.getChain();
        if((!Objects.equals(useraddr, user1_addr)) && (!Objects.equals(useraddr, user2_addr))){
            return "address not found";
        }
        if(key.length()<8){
            return "key length error";
        }
        if(chain.length()!=32){
            return "chain length error";
        }
        //用户存储到map中
        userBody.setPkRoot(key);
        userBody.setChainRoot(chain);
        byte[] pk=Hex.decode(key);
        userBody.addpkhash(KeyDerive.Sm3Hash(pk));
        usermap.put(useraddr,userBody);

        System.out.println("Initial user: "+userBody);
        return "Initial user: "+userBody;
    }

//    测试注册公钥，不改变usermap
    @PostMapping("/registerPk")
    public ResponseBody registerPk(@RequestBody UserBody userBody) throws Exception {
        ResponseBody response = new ResponseBody();
        String CAtoken=userBody.getToken();
        if(!Objects.equals(CAtoken,ca_addr)){
            response.setMessage("token error");
            return response;
        }
        String useraddr= userBody.getAddress();
        UserBody user= usermap.get(useraddr);
        if(user==null){
            response.setMessage("user not found");
            return response;
        }
        String key= user.getKey();
        byte[] publicKey=Hex.decode(key);

        long startTime = System.nanoTime();
        String txid=transaction.Register_PublicKey(publicKey,map_addr,client);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000_000.0;

        System.out.println("registerPk: "+duration+"s");


        BcosTransaction tr=client.getTransactionByHash(txid);
        String transresponse=tr.getResult().toString();

        response.setTransaction(transresponse);
        response.setPublicKey(key);
        response.setRuntime(duration+"s");
        response.setChain(user.getChain());
        response.setKeyindex(user.getKeyIndex());
        response.setMessage("成功注册公钥：生成公钥证书，将公钥证书嵌入到交易发布到区块链上，更新合约内容"+"registerPk: "+duration+"s");
        response.setStatus(1);
        return response;
    }



    //仅测试密钥派生功能，不改变usermap和区块链状态
    @PostMapping("/pkDerive")
    public ResponseBody pkDerive(@RequestBody UserBody userBody)throws Exception{
        ResponseBody responseBody =new ResponseBody();
        String useraddr= userBody.getAddress();
        UserBody user= usermap.get(useraddr);
        if(user==null){
            responseBody.setMessage("user not found");
            return responseBody;
        }
        String publicKey= user.getKey();
        String chain=user.getChain();
        int keyIndex=user.getKeyIndex();

        byte[] pk= Util.hexStr2Bytes(publicKey);
        byte[] chainbyte=Util.hexStr2Bytes(chain);

        long startTime = System.nanoTime();
        byte[][] up= KeyDerive.SM2_PublicKeyDerive(pk,keyIndex,chainbyte);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;
        System.out.println("SM2_PublicKeyDerive: "+duration+"ms");

        String uppk=Util.byte2HexStr(up[0]);
        String upchain=Util.byte2HexStr(up[1]);


        responseBody.setMessage("SM2_PublicKeyDerive: "+duration+"ms");
        responseBody.setChain(upchain);
        responseBody.setPublicKey(uppk);
        responseBody.setRuntime(duration+"ms");
        responseBody.setStatus(1);
        return responseBody;
    }


    //录制视频使用，派生数量（batch）固定为5
    @PostMapping("/BatchRegisterPk")
    public ResponseBody BatchRegisterPk(@RequestBody UserBody userBody)throws Exception{
        ResponseBody response =new ResponseBody();
        String CAtoken=userBody.getToken();
        if(!Objects.equals(CAtoken,ca_addr)){
            response.setMessage("token error");
            return response;
        }
        String useraddr= userBody.getAddress();
        UserBody user= usermap.get(useraddr);
        if(user==null){
            response.setMessage("user not found");
            return response;
        }

        String publicKey= user.getKey();
        String chain=user.getChain();
        int keyIndex=user.getKeyIndex();

        byte[] pk= Hex.decode(publicKey);
        byte[] chainbyte=Hex.decode(chain);


        String txid="";
        System.out.println("----------------------BatchRegisterPk Begin------------------");
        long startTime = System.nanoTime();
        for(int i=0;i<5;i++){
            byte[][] up= KeyDerive.SM2_PublicKeyDerive(pk,keyIndex,chainbyte);
            pk=up[0];
            chainbyte=up[1];
            keyIndex=keyIndex+1;
            byte[] newpk=up[0];
            user.addpkhash(KeyDerive.Sm3Hash(newpk));
            txid=transaction.Register_PublicKey(newpk,map_addr,client);
        }
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000_000.0;
        System.out.println("----------------------BatchRegisterPk END-------------------");
        System.out.println("BatchRegisterPk: "+duration+"s");

        //更新usermap
        user.setChain(Util.byte2HexStr(chainbyte));
        user.setKey(Util.byte2HexStr(pk));
        user.setKeyIndex(keyIndex);
        usermap.put(useraddr,user);

        String transresponse=client.getTransactionByHash(txid).getResult().toString();
        response.setMessage("成功派生密钥五次，并注册公钥"+" BatchRegisterPk: "+duration+"s");

        //回显放入派生公钥链尾端公钥（最新公钥）
        response.setPublicKey(user.getKey());
        response.setTransaction(transresponse);
        response.setRuntime(duration+"s");
        response.setChain(user.getChain());
        response.setKeyindex(user.getKeyIndex());
        response.setStatus(1);
        return response;
    }

    //将BatchRegisterPk封装成函数测试，可指定派生数量(batch)
    @PostMapping("/BatchRegistertest")
    public ResponseBody BatchRegistertest(@RequestBody BatchRequest batchRequest)throws Exception{
        ResponseBody response =new ResponseBody();
        String CAtoken=batchRequest.getToken();
        if(!Objects.equals(CAtoken,ca_addr)){
            response.setMessage("token error");
            return response;
        }

        String useraddr= batchRequest.getAddress();

        int batch=batchRequest.getBatch();

        UserBody user= usermap.get(useraddr);
        if(user==null){
            response.setMessage("user not found");
            return response;
        }

        byte[] publicKey= Hex.decode(user.getKey());
        byte[] chain=Hex.decode(user.getChain());
        int keyIndex=user.getKeyIndex();


        long startTime = System.nanoTime();
        user=transaction.BatchRegisterPk(publicKey,chain,keyIndex,batch,map_addr,client,user);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000_000.0;
        System.out.println("BatchRegisterPk: "+duration+"s");

        //更新usermap
        usermap.put(useraddr,user);

        response.setMessage("成功派生密钥"+batch+"次"+",并注册公钥"+" BatchRegisterPk: "+duration+"s."+"当前公钥链长度："+user.getPkhashList().size());

        //回显放入派生公钥链尾端公钥（最新公钥）
        response.setPublicKey(user.getKey());
        response.setRuntime(duration+"s");

        //以String形式用于展示当前pkhashlist
        List<String> PKhashlist=new ArrayList<>();
        for(byte[] pkhash:user.getPkhashList()){
            PKhashlist.add(Hex.toHexString(pkhash));
        }
        response.setPkhashlist(PKhashlist);
        response.setStatus(1);
        return response;
    }


    //撤销公钥
    @PostMapping("/revokePk")
    public ResponseBody revokePk(@RequestBody UserBody userBody)throws Exception{
        ResponseBody response =new ResponseBody();
        String CAtoken=userBody.getToken();
        if(!Objects.equals(CAtoken,ca_addr)){
            response.setMessage("token error");
            return response;
        }

        String publicKey= userBody.getKey();
        long startTime = System.nanoTime();
        boolean flag=transaction.Revoke_PublicKey(Hex.decode(publicKey),map_addr,client);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;
        System.out.println("Revoke_PublicKey: "+duration+"ms");

        if(flag){
            response.setMessage("true");
        }else {
            response.setMessage("false");
        }
        response.setRuntime(duration+"ms");
        response.setStatus(1);
        return response;
    }


    //撤销用户
    @PostMapping("/revokeUser")
    public ResponseBody revokeUser(@RequestBody UserBody userBody)throws Exception {
        ResponseBody response = new ResponseBody();
        String CAtoken = userBody.getToken();
        if (!Objects.equals(CAtoken, ca_addr)) {
            response.setMessage("token error");
            return response;
        }
        String useraddr= userBody.getAddress();
        UserBody user= usermap.get(useraddr);
        if (user == null) {
            response.setMessage("user not found");
            return response;
        }
        long startTime = System.nanoTime();
        transaction.Revoke_user(user,map_addr,client);
        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000_000.0;
        System.out.println("Revoke_user: "+duration+"s");

        response.setRuntime(duration+"ms");

        response.setMessage("Revoke_user: "+duration+"s."+" 撤销公钥数量："+user.getPkhashList().size());
        response.setStatus(1);
        return response;
    }


    @PostMapping("/getpkhash")
    public String hashtest(@RequestBody UserBody userBody) throws Exception {
        String key= userBody.getKey();
        byte[] pk= Hex.decode(key);
        byte[] hash=KeyDerive.Sm3Hash(pk);
        return Hex.toHexString(hash);
    }

    @PostMapping("/getuser")
    public ResponseBody Getuser(@RequestBody UserBody userBody) throws Exception{
        String useraddr= userBody.getAddress();
        String CAtoken = userBody.getToken();
        ResponseBody response = new ResponseBody();
        if (!Objects.equals(CAtoken, ca_addr)) {
            response.setMessage("token error");
            return response;
        }

        UserBody user= usermap.get(useraddr);
        if (user == null) {
            response.setMessage("user not found");
            return response;
        }
        response.setMessage("user address="+useraddr);
        response.setPublicKey(user.getKey());
        response.setChain(user.getChain());
        response.setKeyindex(user.getKeyIndex());
        response.setUser(user);
        response.setStatus(1);
        return response;
    }

    @PostMapping("/traceuser")
    public ResponseBody traceUser(@RequestBody UserBody userBody)throws Exception{
        ResponseBody response = new ResponseBody();
        String CAtoken = userBody.getToken();
        if (!Objects.equals(CAtoken, ca_addr)) {
            response.setMessage("token error");
            return response;
        }
        String key= userBody.getKey();


        long startTime = System.nanoTime();
        byte[] pk= Hex.decode(key);
        byte[] pkhash=KeyDerive.Sm3Hash(pk);
        boolean flag=false;
        for(UserBody user:usermap.getMap().values()){
            List<byte[]> list=user.getPkhashList();
            for(byte[] hash:list){
                if(Arrays.equals(hash,pkhash)){
                    response.setMessage("user address="+user.getAddress());
                    response.setUser(user);
                    flag=true;
                }
            }
        }

        long endTime =  System.nanoTime();
        double duration = (endTime - startTime) / 1_000_000.0;
        System.out.println("TraceUser: "+duration+"ms");
        response.setRuntime(duration+"ms");
        if(!flag){
            response.setMessage("user not found");
            return response;
        }
        response.setStatus(1);
        return response;
    }





























}
